<?php

class CreditCalculator
{
    const DAY_INC = 0.010960749;

    protected $rate = 12;
    protected $credit_total = 4294800;
    protected $monthes_total = 120;
    protected $date_first = '2014-05-27';
//    protected $credit_total = 3908006.9;
//    protected $monthes_total = 101;
//    protected $date_first = '2015-12-27';

    protected $month_payment = 0;
    protected $total_payments = 0;
    protected $day_inc = 0;

    protected $years_day_inc_map = array(
        '2016' => 0.010960749,
        '2017' => -0.010923433,
        '2020' => 0.010960015,
        '2021' => -0.010927949,
        '2024' => 0.010961565,
    );

    protected function init()
    {
        $this->month_payment = $this->calcMonthPayment($this->credit_total, $this->monthes_total);
    }

    protected function round($value)
    {
        return round($value, 2);
    }

    protected function calcMonthPayment($credit_total, $monthes_total)
    {
        return $this->round((($credit_total * $this->rate) / (12 * 100)) / (1 - pow((1 + ($this->rate / (12 * 100))), (-1 * $monthes_total))));
    }

    protected function calcMonthPercent($credit_total, $period_days, $year_days)
    {
        return $this->round($credit_total * $this->rate * $period_days / $year_days / 100);
    }

    protected function calcYearDays($payment_date)
    {
        $start_year = date('Y', strtotime($payment_date));
        $start_date = strtotime($start_year . '-01-01');
        $end_date = strtotime(($start_year + 1) . '-01-01');
        return ($end_date - $start_date) / (60 * 60 * 24);
    }
/*
    protected function calcPaymentsMap($credit_total, $monthes_total, $date_first)
    {
        $result = array();
        $month_ind = 0;
        $start_date = date('d.m.Y', strtotime($date_first));
        $total_percent = $total_for_credit = $total_payment =0;
        while ($monthes_total) {
            ++$month_ind;

            $payment_date = date('d.m.Y', strtotime($start_date . ' +1 month'));
            $payment_date_timestamp = strtotime($payment_date);
            $period_days = ($payment_date_timestamp - strtotime($start_date)) / (60 * 60 * 24);
            $year_days = $this->calcYearDays($payment_date);

            $curr_month = date('m', $payment_date_timestamp);
            $curr_year = date('Y', $payment_date_timestamp);
            if ($curr_month === '01' && isset($this->years_day_inc_map[$curr_year])) {
                $period_days += $this->years_day_inc_map[$curr_year];
            }
//            echo '<xmp>' . print_r(array($period_days, $year_days), 1) . '</xmp>';

            $month_percent = $this->calcMonthPercent($credit_total, $period_days, $year_days);
            $month_for_credit = $this->month_payment - $month_percent;
            $credit_rest = $this->round($credit_total - $month_for_credit);

            $month_payment = $this->month_payment;

            if ($credit_rest < ($this->month_payment / 3)) {
                $month_for_credit += $credit_rest;
                $month_payment += $credit_rest;
                $credit_rest = 0;
            }

            $credit_total = $credit_rest;

            $result[$payment_date_timestamp] = $this->makePaymentMapRow($month_ind, $payment_date, $month_for_credit, $month_percent, $month_payment, $credit_rest);
            $total_for_credit += $month_for_credit;
            $total_percent += $month_percent;
            $total_payment += $month_payment;
//            echo '<xmp>' . print_r($result, 1) . '</xmp>';
//            exit;
            $start_date = $payment_date;
            --$monthes_total;

//            if ($month_ind > 3) break;
        }
*/
    protected function calcPaymentsMap($credit_total, $monthes_total, $date_first)
    {
        $result = array();
        $month_ind = 0;
        $start_date = date('d.m.Y', strtotime($date_first));
        $total_percent = $total_for_credit = $total_payment =0;
        while ($monthes_total) {
            ++$month_ind;

            $payment_date = date('d.m.Y', strtotime($start_date . ' +1 month'));
            $payment_date_timestamp = strtotime($payment_date);
            $period_days = ($payment_date_timestamp - strtotime($start_date)) / (60 * 60 * 24);
            $year_days = $this->calcYearDays($payment_date);

            $curr_month = date('m', $payment_date_timestamp);
            $curr_year = date('Y', $payment_date_timestamp);
            if ($curr_month === '01' && isset($this->years_day_inc_map[$curr_year])) {
                $period_days += $this->years_day_inc_map[$curr_year];
            }
//            echo '<xmp>' . print_r(array($period_days, $year_days), 1) . '</xmp>';

            $month_percent = $this->calcMonthPercent($credit_total, $period_days, $year_days);
            $month_for_credit = $this->month_payment - $month_percent;
            $credit_rest = $this->round($credit_total - $month_for_credit);

            $month_payment = $this->month_payment;

            if ($credit_rest < ($this->month_payment / 3)) {
                $month_for_credit += $credit_rest;
                $month_payment += $credit_rest;
                $credit_rest = 0;
            }

            $credit_total = $credit_rest;

            $result[$payment_date_timestamp] = $this->makePaymentMapRow($month_ind, $payment_date, $month_for_credit, $month_percent, $month_payment, $credit_rest);




            $start_date = $payment_date;
            --$monthes_total;

//            if ($month_ind > 3) break;
        }

        return $result;
    }

    protected function calcTotal(&$payments_map)
    {
        $result =array(
            'total_for_credit' => 0,
            'total_percent' => 0,
            'total_payment' => 0,
        );
        foreach ($payments_map as $payment) {
            $result['total_for_credit'] += $payment['month_for_credit'];
            $result['total_percent'] += $payment['month_percent'];
            $result['total_payment'] += $payment['month_payment'];
        }
        return $result;
    }

    protected function findNearestPayments($payments_map, $date)
    {
        $result = array(
            'prev' => array(
                'payment_date' => date('d.m.Y', strtotime($this->date_first)),
                'credit_rest' => $this->credit_total,
            ),
            'next' => array(),
        );
        foreach ($payments_map as $dt => $value) {
            if ($date <= $dt) {
                $result['next'] = $value;
                return $result;
            }
            if ($date > $dt) {
                $result['prev'] = $value;
            }
        }
    }

    protected function daysDiff($date_from, $date_to)
    {
        return (($date_to - $date_from) / (60 * 60 * 24));
    }

    protected function makePaymentMapRow($month_ind, $payment_date, $month_for_credit, $month_percent, $month_payment, $credit_rest)
    {
        return array(
            'ind' => $month_ind,
            'payment_date' => $payment_date,
            'month_for_credit' => $month_for_credit,
            'month_percent' => $month_percent,
            'month_payment' => $month_payment,
            'month_payment2' => $this->month_payment,
            'credit_rest' => $credit_rest,
        );
    }

    protected function makePrePayment($payments_map, $date, $amount)
    {
        $result = array();
        $date_dt = strtotime($date);
        $date_date = date('d.m.Y', $date_dt);
        $nearest_payments = $this->findNearestPayments($payments_map, $date_dt);
        $credit_total = $nearest_payments['prev']['credit_rest'];
        $year_days = $this->calcYearDays($date_date);
        $day_percent = $this->rate / 100 / $year_days;
        $day_payment = $credit_total * $day_percent;
        $days_credit = $this->daysDiff(strtotime($nearest_payments['prev']['payment_date']), $date_dt);
        $percent_for_days = $this->round($day_payment * $days_credit);
        if ($amount >= ($credit_total + $percent_for_days)) {
            $credit_for_days = $credit_total;
        } else {
            $credit_for_days = $amount - $percent_for_days;
        }
        $credit_rest = $credit_total - $credit_for_days;

        $result[$date_dt] = $this->makePaymentMapRow(0, $date_date, $credit_for_days, $percent_for_days, $amount, $credit_rest);

        $next_dt = strtotime($nearest_payments['next']['payment_date']);
        if ($next_dt != $date_dt) {
            $credit_for_days_next = ($nearest_payments['next']['month_for_credit'] > $credit_for_days) ? $nearest_payments['next']['month_for_credit'] - $credit_for_days : 0.00;
            $days_to_next = $this->daysDiff($date_dt, $next_dt);
            $day_payment_next = $credit_rest * $day_percent;
            $percent_for_days_next = $this->round($day_payment_next * $days_to_next);
            $result[$next_dt] = $this->makePaymentMapRow(
                0,
                $nearest_payments['next']['payment_date'],
                $credit_for_days_next,
                $percent_for_days_next,
                $credit_for_days_next + $percent_for_days_next,
                $credit_rest
            );
        }

        return $result;
    }

    protected function recalcMapByPrepayment($payments_map, $prepayment)
    {
        $result = array();
        end($prepayment);
        $prepayment_dt = key($prepayment);

        $ind = 0;
        foreach ($payments_map as $key => $payment) {
            ++$ind;
            if ($key == $prepayment_dt) {
                $result += $prepayment;
                break;
            }
            $result[$key] = $payment;
        }

        $last_row = end($result);
        //$monthes_total = $this->monthes_total - $ind;
        $monthes_total = sizeof($payments_map) - $ind;
        $this->month_payment = $this->calcMonthPayment($last_row['credit_rest'], $monthes_total);
        //echo '<xmp>' . print_r(array($this->month_payment, $last_row['credit_rest'], $monthes_total), true) . '</xmp>';
        //exit;
        $payments_map = $this->calcPaymentsMap($last_row['credit_rest'], $monthes_total, $last_row['payment_date']);

        $result += $payments_map;

        return $result;
    }

    protected function calcTotalDiff($total_1, $total_2)
    {
        $result = array();
        foreach ($total_1 as $key => $value) {
            $result[$key] = $this->round($value - $total_2[$key]);
        }
        return $result;
    }

    protected function loopPrepayment($payments_map, $date_day, $amount)
    {
        $payments_map_copy = $payments_map;
//        $loops = sizeof($payments_map);
        //for ($i = 0; $i < $loops; $i++) {}
//        $first_payment = reset($payments_map);
//        $date = preg_replace('/(\d{1,2})\.(\d{1,2})\.(\d{4})/', '\3-\2-' . $date_day, $first_payment['payment_date']);
        $ind = 0;
//        for ($i = 0; $i < $loops; $i++) {
        foreach ($payments_map_copy as $payment) {
            ++$ind;

            $date = preg_replace('/(\d{1,2})\.(\d{1,2})\.(\d{4})/', '\3-\2-' . $date_day, $payment['payment_date']);
            $prepayment = $this->makePrePayment($payments_map, $date, $amount);
            $payments_map = $this->recalcMapByPrepayment($payments_map, $prepayment);

//            $date = date('Y-m-d', strtotime($date . '+1 month'));
            if ($ind == 68) {
                echo '<xmp>' . print_r($payments_map, true) . '</xmp>' . $ind . PHP_EOL;;
                exit;
            }
//            $total_2 = $this->calcTotal($payments_map);
        }
    }

    public function run($params)
    {
//        echo '<xmp>' . print_r($this->calcMonthPayment(, $monthes_total), true) . '</xmp>';
//        exit;

        $this->init();

        $payments_map = $this->calcPaymentsMap($this->credit_total, $this->monthes_total, $this->date_first);
        $total_1 = $this->calcTotal($payments_map);

//        $this->loopPrepayment($payments_map, 18, 80000);
        

        $prepayment = $this->makePrePayment($payments_map, '2014-06-18', 80000);
        $payments_map = $this->recalcMapByPrepayment($payments_map, $prepayment);
        $total_2 = $this->calcTotal($payments_map);

        $total_diff2 = $this->calcTotalDiff($total_1, $total_2);

        $prepayment = $this->makePrePayment($payments_map, '2014-07-02', 60000);
        $payments_map = $this->recalcMapByPrepayment($payments_map, $prepayment);
        $total_3 = $this->calcTotal($payments_map);
        $total_diff3 = $this->calcTotalDiff($total_2, $total_3);

//        echo '<xmp>' . print_r(array($payments_map, $total_diff3), true);
//        exit;
        $total_diff4 = $this->calcTotalDiff($total_1, $total_3);
        echo '<xmp>' . print_r(array($total_1, $total_2, $total_3 , $total_diff2, $total_diff3, $total_diff4), 1) . '</xmp>';
        exit;
    }
}